package project.parser.ast.exps;

import project.environments.*;
import project.parser.EvalException;
import project.parser.ast.aux.*;
import project.parser.ast.types.FuncType;
import project.parser.ast.types.Type;
import project.parser.ast.values.FuncValue;

public class FuncDec extends FuncAbstract implements Exp {
	private StaticEnv senv;

	private final Ident id;
	private final Stats stats;
	private final Exp retExp;

	public FuncDec(Ident id, Params params, Stats stats, Exp retExp) {
		super(params);
		this.id = id;
		this.stats = stats;
		this.retExp = retExp;
		this.retType = null;

		this.senv = new StaticEnvClass(); //inizializza l'ambiente statico con i tipi dei parametri
		for (Param param : params)
			this.senv.create(param.getId(), param.getType());
	}

	@Override
	public Type typecheck(StaticEnv senv) { //ritorna e crea il tipo statico della funzione
		if (retType != null) //se ho gia' calcolato il tipo di ritorno non l'ho ricalcolo
			return retType;
		stats.typecheckList(this.senv);
		retType = retExp.typecheck(this.senv);
		FuncType funcType = new FuncType(params, retType);
		senv.funcCreate(id, funcType);
		return funcType;
	}

	@Override
	public Value evaluate(DynamicEnv denv) { //ritorna e crea un nuovo FuncValue nell'ambiente dinamico passato
		FuncValue funcValue = new FuncValue(this);
		denv.funcCreate(id, funcValue);
		return funcValue;
	}

	public Value call(DynamicEnv denv, Args args) { //valuta gli argomenti e la funzione
		DynamicEnv funcDenv = new DynamicEnvClass(); //inizializzazione dell'ambiente dinamico della funzione
		if (args.length() != params.length()) //controlla che il numero degli argomenti passati sia giusto
			throwParamTypeException();
		int i = 0, length = args.length();
		for (; i < length; i++) {
			Value argsValue = args.get(i).evaluate(denv); //valuta gli argomenti nell'ambiente passato
			if (!argsValue.type().equals(params.get(i).getType())) //controlla che il tipo degli argomenti sia giusto
				throwParamTypeException();
			funcDenv.create(params.get(i).getId(), argsValue); //crea il nuovo argomento nell'ambiente della funzione
		}
		stats.evaluateList(funcDenv); //valuta il corpo della funzione
		return retExp.evaluate(funcDenv); //valuta e ritorna l'espressione di ritorno
	}

	private void throwParamTypeException() {
		throw new EvalException("Function call '" + id + "' has wrong argument types");
	}
}
